/*
  +----------------------------------------------------------------------+
  | Swoole                                                               |
  +----------------------------------------------------------------------+
  | This source file is subject to version 2.0 of the Apache license,    |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.apache.org/licenses/LICENSE-2.0.html                      |
  | If you did not receive a copy of the Apache2.0 license and are unable|
  | to obtain it through the world-wide-web, please send a note to       |
  | license@swoole.com so we can mail you a copy immediately.            |
  +----------------------------------------------------------------------+
  | Author: Tianfeng Han  <rango@swoole.com>                             |
  +----------------------------------------------------------------------+
*/

#include "php_swoole_http_server.h"

#include <memory>
#include <sstream>

#include "swoole_static_handler.h"

#include "main/php_variables.h"

using namespace swoole;
using std::string;
using swoole::coroutine::System;
using swoole::http2::get_default_setting;
using swoole::http_server::StaticHandler;

namespace Http2 = swoole::http2;

using HttpContext = swoole::http::Context;
using Http2Stream = Http2::Stream;
using Http2Session = Http2::Session;

static SW_THREAD_LOCAL std::unordered_map<SessionId, std::shared_ptr<Http2Session>> http2_sessions;

static bool http2_server_respond(HttpContext *ctx, const String *body);
static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler);
static bool http2_server_send_status_code(HttpContext *ctx, int status_code);

Http2Stream::Stream(const Http2Session *client, uint32_t _id) {
    ctx = swoole_http_context_new(client->fd);
    ctx->copy(client->default_ctx);
    ctx->http2 = true;
    ctx->stream_id = _id;
    ctx->keepalive = true;
    id = _id;
    local_window_size = client->local_settings.init_window_size;
    remote_window_size = client->remote_settings.init_window_size;
}

Http2Stream::~Stream() {
    ctx->stream_id = 0;
    ctx->end_ = true;
    ctx->free();
}

void Http2Stream::reset(uint32_t error_code) const {
    char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE];
    swoole_trace_log(
        SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, error_code=%u", "RST_STREAM", id, error_code);
    *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code);
    set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, SW_HTTP2_RST_STREAM_SIZE, 0, id);
    ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE);
}

Http2Session::Session(SessionId _fd) {
    fd = _fd;
    init_settings(&local_settings);
    // [init]: we must set default value, peer is not always send all the settings
    init_settings(&remote_settings);
    local_window_size = local_settings.init_window_size;
    remote_window_size = remote_settings.init_window_size;
    last_stream_id = 0;
    shutting_down = false;
    is_coro = false;
    max_body_size = 0;
}

std::shared_ptr<Http2Stream> Http2Session::get_stream(uint32_t stream_id) {
    auto iter = streams.find(stream_id);
    if (iter == streams.end()) {
        return {};
    } else {
        return iter->second;
    }
}

bool Http2Session::remove_stream(uint32_t stream_id) {
    auto iter = streams.find(stream_id);
    if (iter == streams.end()) {
        return false;
    }

    auto stream = iter->second;
    streams.erase(iter);
    return true;
}

std::shared_ptr<Http2Stream> Http2Session::create_stream(uint32_t stream_id) {
    auto stream = std::make_shared<Http2Stream>(this, stream_id);
    streams.emplace(stream_id, stream);
    if (sw_unlikely(!stream->ctx)) {
        swoole_error_log(
            SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 create stream#%d context error", stream_id);
        return {};
    }
    auto ctx = stream->ctx;
    zend::object_set(ctx->request.zobject, ZEND_STRL("streamId"), stream_id);
    return stream;
}

Http2Session::~Session() {
    if (inflater) {
        nghttp2_hd_inflate_del(inflater);
    }
    if (deflater) {
        nghttp2_hd_deflate_del(deflater);
    }
    delete default_ctx;
}

static void http2_server_send_window_update(HttpContext *ctx, uint32_t stream_id, uint32_t size) {
    char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE];
    swoole_trace_log(
        SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, size=%u", "WINDOW_UPDATE", stream_id, size);
    *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size);
    Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, stream_id);
    ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE);
}

static ssize_t http2_server_build_trailer(const HttpContext *ctx, uchar *buffer) {
    zval *ztrailer =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0);
    uint32_t size = php_swoole_array_length_safe(ztrailer);

    if (size > 0) {
        Http2::HeaderSet trailer(size);
        zend_string *key;
        zval *zvalue;

        ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(ztrailer), key, zvalue) {
            if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) {
                continue;
            }
            zend::String str_value(zvalue);
            trailer.add(ZSTR_VAL(key), ZSTR_LEN(key), str_value.val(), str_value.len());
        }
        ZEND_HASH_FOREACH_END();

        ssize_t rv;
        auto client = http2_sessions[ctx->fd];
        auto deflater = client->deflater;

        if (!deflater) {
            int ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem());
            if (ret != 0) {
                swoole_warning("nghttp2_hd_deflate_new2() failed with error: %s", nghttp2_strerror(ret));
                return -1;
            }
            client->deflater = deflater;
        }

        size_t buflen = nghttp2_hd_deflate_bound(deflater, trailer.get(), trailer.len());
#if 0
        if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE) {
            php_swoole_error(E_WARNING,
                             "header cannot bigger than remote max_header_list_size %u",
                             SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE);
            return -1;
        }
#endif
        rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, trailer.get(), trailer.len());
        if (rv < 0) {
            swoole_warning("nghttp2_hd_deflate_hd() failed with error: %s", nghttp2_strerror((int) rv));
            return -1;
        }
        return rv;
    }
    return 0;
}

static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) {
    zval *zserver = ctx->request.zserver;
    zval *zrequest_uri = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_uri"));
    if (zrequest_uri && Z_TYPE_P(zrequest_uri) == IS_STRING) {
        StaticHandler handler(serv, Z_STRVAL_P(zrequest_uri), Z_STRLEN_P(zrequest_uri));
        if (!handler.try_serve()) {
            return false;
        }

        if (handler.status_code == SW_HTTP_NOT_FOUND) {
            String body(SW_STRL(SW_HTTP_PAGE_404));
            ctx->response.status = SW_HTTP_NOT_FOUND;
            http2_server_respond(ctx, &body);
            return true;
        }

        /**
         * if http_index_files is enabled, need to search the index file first.
         * if the index file is found, set filename to index filename.
         */
        if (!handler.try_serve_index_file()) {
            return false;
        }

        /**
         * the index file was not found in the current directory,
         * if http_autoindex is enabled, should show the list of files in the current directory.
         */
        if (!handler.has_index_file() && handler.is_enabled_auto_index() && handler.is_dir()) {
            String body(PATH_MAX);
            body.length = handler.make_index_page(&body);
            http2_server_respond(ctx, &body);
            return true;
        }

        auto date_str = handler.get_date();
        auto date_str_last_modified = handler.get_date_last_modified();

        zval *zheader = ctx->request.zheader;
        ctx->set_header(ZEND_STRL("Last-Modified"), date_str_last_modified, false);

        zval *zdate_if_modified_since = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-modified-since"));
        if (zdate_if_modified_since) {
            string date_if_modified_since(Z_STRVAL_P(zdate_if_modified_since), Z_STRLEN_P(zdate_if_modified_since));
            if (!date_if_modified_since.empty() && handler.is_modified(date_if_modified_since)) {
                ctx->response.status = SW_HTTP_NOT_MODIFIED;
                return true;
            }
        }

        zval *zrange = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("range"));
        zval *zif_range = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-range"));
        handler.parse_range(zrange ? Z_STRVAL_P(zrange) : nullptr, zif_range ? Z_STRVAL_P(zif_range) : nullptr);
        ctx->response.status = handler.status_code;
        auto tasks = handler.get_tasks();
        if (1 == tasks.size()) {
            if (SW_HTTP_PARTIAL_CONTENT == handler.status_code) {
                std::stringstream content_range;
                content_range << "bytes " << tasks[0].offset << "-" << (tasks[0].length + tasks[0].offset - 1) << "/"
                              << handler.get_filesize() << "\r\n";
                auto content_range_str = content_range.str();
                ctx->set_header(ZEND_STRL("Content-Range"), content_range_str, false);
            } else {
                ctx->set_header(ZEND_STRL("Accept-Ranges"), SW_STRL("bytes"), false);
            }
        }

        ctx->onAfterResponse = nullptr;
        ctx->onBeforeRequest = nullptr;

        // request_method
        zval *zrequest_method = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_method"));
        if (zrequest_method && Z_TYPE_P(zrequest_method) == IS_STRING &&
            SW_STRCASEEQ(Z_STRVAL_P(zrequest_method), Z_STRLEN_P(zrequest_method), "HEAD")) {
            String empty_body;
            http2_server_respond(ctx, &empty_body);
            return true;
        } else {
            return http2_server_send_range_file(ctx, &handler);
        }
    }

    return false;
}

static void http2_server_onRequest(const std::shared_ptr<Http2Session> &client,
                                   const std::shared_ptr<Http2Stream> &stream) {
    HttpContext *ctx = stream->ctx;
    zval *zserver = ctx->request.zserver;
    auto serv = ctx->get_async_server();
    zval args[2];
    Connection *serv_sock = nullptr;
    zend::Callable *cb = nullptr;
    int server_fd = 0;

    Connection *conn = serv->get_connection_by_session_id(ctx->fd);
    if (!conn) {
        goto _destroy;
    }

    server_fd = conn->server_fd;
    serv_sock = serv->get_connection(server_fd);

    ctx->request.version = SW_HTTP_VERSION_2;

    if (serv->enable_static_handler && http2_server_is_static_file(serv, ctx)) {
        goto _destroy;
    }

    add_assoc_long(zserver, "request_time", time(nullptr));
    add_assoc_double(zserver, "request_time_float", microtime());
    if (serv_sock) {
        add_assoc_long(zserver, "server_port", serv_sock->info.get_port());
    }
    add_assoc_long(zserver, "remote_port", conn->info.get_port());
    add_assoc_string(zserver, "remote_addr", (char *) conn->info.get_addr());
    add_assoc_long(zserver, "master_time", conn->last_recv_time);
    add_assoc_string(zserver, "server_protocol", (char *) "HTTP/2");

    cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onRequest);
    ctx->private_data_2 = cb;

    if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) {
        return;
    }

    args[0] = *ctx->request.zobject;
    args[1] = *ctx->response.zobject;
    if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) {
        stream->reset(SW_HTTP2_ERROR_INTERNAL_ERROR);
        php_swoole_error(E_WARNING, "%s->onRequest[v2] handler error", ZSTR_VAL(swoole_http_server_ce->name));
    }

_destroy:
    zval_ptr_dtor(ctx->request.zobject);
    zval_ptr_dtor(ctx->response.zobject);
}

static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const String *body) {
    zval *zheader =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0);
    zval *zcookie =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0);
    Http2::HeaderSet headers(32 + php_swoole_array_length_safe(zheader) + php_swoole_array_length_safe(zcookie));
    char intbuf[2][16];

    assert(ctx->send_header_ == 0);

    // status code
    if (ctx->response.status == 0) {
        ctx->response.status = SW_HTTP_OK;
    }
    int ret = swoole_itoa(intbuf[0], ctx->response.status);
    headers.add(ZEND_STRL(":status"), intbuf[0], ret);

    uint32_t header_flags = 0x0;

    // headers
    if (ZVAL_IS_ARRAY(zheader)) {
        const char *key;
        uint32_t keylen;
        zval *zvalue;
        int type;

        zend_string *content_type = nullptr;
        auto add_header =
            [ctx, &content_type](
                Http2::HeaderSet &headers, const char *key, size_t l_key, zval *value, uint32_t &header_flags) {
                if (ZVAL_IS_NULL(value)) {
                    return;
                }
                zend::String str_value(value);
                str_value.rtrim();
                if (swoole_http_has_crlf(str_value.val(), str_value.len())) {
                    return;
                }
                if (SW_STRCASEEQ(key, l_key, "server")) {
                    header_flags |= HTTP_HEADER_SERVER;
                } else if (SW_STRCASEEQ(key, l_key, "content-length")) {
                    return;  // ignore
                } else if (SW_STRCASEEQ(key, l_key, "date")) {
                    header_flags |= HTTP_HEADER_DATE;
                } else if (SW_STRCASEEQ(key, l_key, "content-type")) {
                    header_flags |= HTTP_HEADER_CONTENT_TYPE;
#ifdef SW_HAVE_COMPRESSION
                    if (ctx->accept_compression && ctx->compression_types) {
                        content_type = zval_get_string(value);
                    }
#endif
                }
                headers.add(key, l_key, str_value.val(), str_value.len());
            };

        SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheader), key, keylen, type, zvalue) {
            if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) {
                continue;
            }
            if (ZVAL_IS_ARRAY(zvalue)) {
                zval *zvalue_2;
                SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) {
                    add_header(headers, key, keylen, zvalue_2, header_flags);
                }
                SW_HASHTABLE_FOREACH_END();
            } else {
                add_header(headers, key, keylen, zvalue, header_flags);
            }
        }
        SW_HASHTABLE_FOREACH_END();
        (void) type;

#ifdef SW_HAVE_COMPRESSION
        if (ctx->accept_compression && ctx->compression_types) {
            std::string str_content_type = content_type ? std::string(ZSTR_VAL(content_type), ZSTR_LEN(content_type))
                                                        : std::string(ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE));
            ctx->accept_compression = ctx->compression_types->find(str_content_type) != ctx->compression_types->end();
            if (content_type) {
                zend_string_release(content_type);
            }
        }
#endif
    }

    if (!(header_flags & HTTP_HEADER_SERVER)) {
        headers.add(ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE));
    }
    if (!(header_flags & HTTP_HEADER_DATE)) {
        auto date_str = php_swoole_http_get_date();
        headers.add(ZEND_STRL("date"), ZSTR_VAL(date_str), ZSTR_LEN(date_str));
    }
    if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) {
        headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html"));
    }

    // cookies
    if (ZVAL_IS_ARRAY(zcookie)) {
        zval *zvalue;
        SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zcookie), zvalue) {
            if (Z_TYPE_P(zvalue) != IS_STRING) {
                continue;
            }
            headers.add(ZEND_STRL("set-cookie"), Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue));
        }
        SW_HASHTABLE_FOREACH_END();
    }

    if (body) {
        size_t content_length = body->length;
        // content length
#ifdef SW_HAVE_COMPRESSION
        if (ctx->compress(body->str, body->length)) {
            content_length = ctx->zlib_buffer->length;
            // content encoding
            const char *content_encoding = ctx->get_content_encoding();
            headers.add(ZEND_STRL("content-encoding"), (char *) content_encoding, strlen(content_encoding));
        }
#endif
        ret = swoole_itoa(intbuf[1], content_length);
        headers.add(ZEND_STRL("content-length"), intbuf[1], ret);
    }

    auto client = http2_sessions[ctx->fd];
    auto deflater = client->deflater;
    if (!deflater) {
        ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem());
        if (ret != 0) {
            swoole_warning("nghttp2_hd_deflate_new2() failed with error: %s", nghttp2_strerror(ret));
            return -1;
        }
        client->deflater = deflater;
    }

    size_t buflen = nghttp2_hd_deflate_bound(deflater, headers.get(), headers.len());
    /*
    if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE)
    {
        php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u",
    SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); return -1;
    }
    */
    ssize_t rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, headers.get(), headers.len());
    if (rv < 0) {
        swoole_warning("nghttp2_hd_deflate_hd() failed with error: %s", nghttp2_strerror((int) rv));
        return -1;
    }

    ctx->send_header_ = 1;
    return rv;
}

bool swoole_http2_server_ping(HttpContext *ctx) {
    char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
    Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0);
    return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
}

static bool http2_server_send_setting_ack(HttpContext *ctx) {
    char frame[SW_HTTP2_FRAME_HEADER_SIZE];
    Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, 0);
    return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE);
}

static bool http2_server_send_rst_stream(HttpContext *ctx, int error_code) {
    char frame[SW_HTTP2_FRAME_HEADER_SIZE + sizeof(uint32_t)];
    Http2::set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, 0, SW_HTTP2_FLAG_ACK, 0);
    *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code);
    return ctx->send(ctx, frame, sizeof(frame));
}

bool swoole_http2_server_goaway(HttpContext *ctx, zend_long error_code, zend_string *sdata) {
    const char *debug_data = sdata ? ZSTR_VAL(sdata) : nullptr;
    size_t debug_data_len = sdata ? ZSTR_LEN(sdata) : 0;
    size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len;
    char *frame = (char *) ecalloc(1, length);
    auto client = http2_sessions[ctx->fd];
    uint32_t last_stream_id = client->last_stream_id;
    Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0);
    *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id);
    *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code);
    if (debug_data_len > 0) {
        memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len);
    }
    bool rv = ctx->send(ctx, frame, length);
    efree(frame);
    client->shutting_down = true;
    return rv;
}

bool Http2Stream::send_header(const String *body, bool end_stream) const {
    char header_buffer[SW_BUFFER_SIZE_STD];
    ssize_t bytes = http2_server_build_header(ctx, (uchar *) header_buffer, body);
    if (bytes < 0) {
        return false;
    }

    String *http_buffer = ctx->get_write_buffer();
    http_buffer->clear();

    /**
     +---------------+
     |Pad Length? (8)|
     +-+-------------+-----------------------------------------------+
     |E|                 Stream Dependency? (31)                     |
     +-+-------------+-----------------------------------------------+
     |  Weight? (8)  |
     +-+-------------+-----------------------------------------------+
     |                   Header Block Fragment (*)                 ...
     +---------------------------------------------------------------+
     |                           Padding (*)                       ...
     +---------------------------------------------------------------+
     */
    char frame_header[SW_HTTP2_FRAME_HEADER_SIZE];

    if (end_stream && (!body || body->length == 0)) {
        http2::set_frame_header(
            frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, id);
        ctx->end_ = 1;
    } else {
        http2::set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS, id);
    }

    http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE);
    http_buffer->append(header_buffer, bytes);

    if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) {
        ctx->send_header_ = 0;
        return false;
    }

    return true;
}

bool Http2Stream::send_end_stream_data_frame() const {
    char frame_header[SW_HTTP2_FRAME_HEADER_SIZE];
    http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, 0, SW_HTTP2_FLAG_END_STREAM, id);
    return ctx->send(ctx, frame_header, SW_HTTP2_FRAME_HEADER_SIZE);
}

bool Http2Stream::send_body(
    const String *body, bool end_stream, const std::shared_ptr<Http2Session> &session, off_t offset, size_t length) {
    char frame_header[SW_HTTP2_FRAME_HEADER_SIZE];
    char *p = body->str + offset;
    size_t l = length == 0 ? body->length : length;

    int flags = end_stream ? SW_HTTP2_FLAG_END_STREAM : SW_HTTP2_FLAG_NONE;
    String *http_buffer = ctx->get_write_buffer();
    auto max_frame_size = session->local_settings.max_frame_size;

    while (l > 0) {
        size_t send_n;
        int _send_flags;
        if (l > max_frame_size) {
            send_n = max_frame_size;
            _send_flags = 0;
        } else {
            send_n = l;
            _send_flags = flags;
        }

        if (send_n > remote_window_size) {
            // The coroutine server receives HTTP2 frames serially and cannot be suspended,
            // therefore it does not support flow control
            // TODO: The coroutine http server also needs to support HTTP2 flow control
            if (ctx->is_co_socket() || !swoole_coroutine_is_in()) {
                swoole_warning("The data sent exceeded remote_window_size");
            } else {
                if (remote_window_size == 0) {
                    waiting_coroutine = Coroutine::get_current();
                    waiting_coroutine->yield();
                    waiting_coroutine = nullptr;
                    continue;
                } else {
                    send_n = remote_window_size;
                    _send_flags = 0;
                }
            }
        }

        http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, send_n, _send_flags, id);

        // send twice to reduce memory copy
        if (send_n < swoole_pagesize()) {
            http_buffer->clear();
            http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE);
            http_buffer->append(p, send_n);
            if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) {
                return false;
            }
        } else {
            if (!ctx->send(ctx, frame_header, SW_HTTP2_FRAME_HEADER_SIZE)) {
                return false;
            }
            if (!ctx->send(ctx, p, send_n)) {
                return false;
            }
        }

        swoole_trace_log(
            SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, flags=%d, send_n=%lu", "DATA", id, flags, send_n);

        l -= send_n;
        p += send_n;

        remote_window_size -= send_n;
        session->remote_window_size -= send_n;
    }

    return true;
}

bool Http2Stream::send_trailer() const {
    char header_buffer[SW_BUFFER_SIZE_STD] = {};
    char frame_header[SW_HTTP2_FRAME_HEADER_SIZE];
    String *http_buffer = ctx->get_write_buffer();

    http_buffer->clear();
    ssize_t bytes = http2_server_build_trailer(ctx, (uchar *) header_buffer);
    if (bytes > 0) {
        http2::set_frame_header(
            frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, id);
        http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE);
        http_buffer->append(header_buffer, bytes);
        if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) {
            return false;
        }
    }

    return true;
}

bool swoole_http2_server_end(HttpContext *ctx, zend_string *sdata) {
    String http_body = {};
    if (sdata) {
        http_body.length = ZSTR_LEN(sdata);
        http_body.str = ZSTR_VAL(sdata);
    } else {
        http_body.length = 0;
        http_body.str = nullptr;
    }
    return http2_server_respond(ctx, &http_body);
}

bool swoole_http2_server_write(HttpContext *ctx, zend_string *sdata) {
    String chunk = {};
    chunk.length = ZSTR_LEN(sdata);
    chunk.str = ZSTR_VAL(sdata);
    if (chunk.length == 0) {
        php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty");
        return false;
    }

    auto client = http2_sessions[ctx->fd];
    auto stream = client->get_stream(ctx->stream_id);

    ctx->send_chunked = 1;

    if (!ctx->send_header_ && !stream->send_header(nullptr, false)) {
        return false;
    }

    if (!stream->send_body(&chunk, false, client)) {
        return false;
    }

    return true;
}

static bool http2_server_respond(HttpContext *ctx, const String *body) {
    auto client = http2_sessions[ctx->fd];
    auto stream = client->get_stream(ctx->stream_id);

    zval *ztrailer =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0);
    if (php_swoole_array_length_safe(ztrailer) == 0) {
        ztrailer = nullptr;
    }

    bool end_stream = (ztrailer == nullptr);

    if (!ctx->send_header_ && !stream->send_header(body, end_stream)) {
        return false;
    }

    // The headers have already been sent, retries are no longer allowed (even if send body failed)
    ctx->end_ = 1;

    bool error = true;

#ifdef SW_HAVE_COMPRESSION
    if (ctx->content_compressed) {
        body = ctx->zlib_buffer.get();
    }
#endif

    SW_LOOP {
        if (ctx->send_chunked && body->length == 0 && !stream->send_end_stream_data_frame()) {
            break;
        } else if (!stream->send_body(body, end_stream, client)) {
            break;
        } else if (ztrailer && !stream->send_trailer()) {
            break;
        }
        error = false;
        break;
    }

    if (error) {
        ctx->close(ctx);
    } else {
        client->remove_stream(stream->id);
    }

    if (client->shutting_down && client->streams.empty()) {
        ctx->close(ctx);
    }

    return !error;
}

static bool http2_server_send_status_code(HttpContext *ctx, int status_code) {
    auto client = http2_sessions[ctx->fd];
    auto stream = client->get_stream(ctx->stream_id);
    ctx->response.status = status_code;
    return stream->send_header(nullptr, true);
}

static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler) {
    auto client = http2_sessions[ctx->fd];
    auto stream = client->get_stream(ctx->stream_id);

#ifdef SW_HAVE_COMPRESSION
    ctx->accept_compression = 0;
#endif
    bool error = false;
    zval *ztrailer =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0);
    if (php_swoole_array_length_safe(ztrailer) == 0) {
        ztrailer = nullptr;
    }
    zval *zheader =
        sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0);
    if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) {
        ctx->set_header(ZEND_STRL("content-type"), handler->get_content_type(), false);
    }

    bool end_stream = (ztrailer == nullptr);
    auto body = std::make_shared<String>();
    body->length = handler->get_content_length();
    if (!stream->send_header(body.get(), end_stream)) {
        return false;
    }

    /* headers has already been sent, retries are no longer allowed (even if send body failed) */
    ctx->end_ = 1;

    auto tasks = handler->get_tasks();
    if (!tasks.empty()) {
        File fp(handler->get_filename(), O_RDONLY);
        if (!fp.ready()) {
            return false;
        }

        char *buf;
        if (tasks.size() > 1) {
            for (auto i = tasks.begin(); i != tasks.end(); ++i) {
                body = std::make_shared<String>(i->part_header, strlen(i->part_header));
                if (!stream->send_body(body.get(), false, client)) {
                    error = true;
                    break;
                }

                fp.set_offset(i->offset);
                buf = (char *) emalloc(i->length);
                auto n_reads = fp.read(buf, i->length);
                if (n_reads < 0) {
                    efree(buf);
                    return false;
                }
                body = std::make_shared<String>(buf, i->length);
                efree(buf);
                if (!stream->send_body(body.get(), false, client)) {
                    error = true;
                    break;
                }
            }

            if (!error) {
                body = std::make_shared<String>(handler->get_end_part());
                if (!stream->send_body(body.get(), end_stream, client)) {
                    error = true;
                }
            }
        } else if (tasks[0].length > 0) {
            auto callback = [&]() -> bool {
                fp.set_offset(tasks[0].offset);
                buf = (char *) emalloc(tasks[0].length);
                auto n_reads = fp.read(buf, tasks[0].length);
                if (n_reads < 0) {
                    efree(buf);
                    return false;
                }
                body = std::make_shared<String>(buf, n_reads);
                efree(buf);
                return true;
            };
            if (swoole_coroutine_is_in()) {
                if (!swoole::coroutine::async(callback)) {
                    return false;
                }
            } else {
                if (!callback()) {
                    return false;
                }
            }
            if (!stream->send_body(body.get(), end_stream, client)) {
                error = true;
            }
        }
    }

    if (!error && ztrailer) {
        if (!stream->send_trailer()) {
            error = true;
        }
    }

    if (error) {
        ctx->close(ctx);
    } else {
        client->remove_stream(ctx->stream_id);
    }

    return true;
}

bool swoole_http2_server_send_file(HttpContext *ctx, zend_string *file, off_t offset, size_t length) {
    auto client = http2_sessions[ctx->fd];
    auto stream = client->get_stream(ctx->stream_id);
    std::shared_ptr<String> body;

#ifdef SW_HAVE_COMPRESSION
    ctx->accept_compression = 0;
#endif
    if (swoole_coroutine_is_in()) {
        body = System::read_file(ZSTR_VAL(file), false);
        if (!body) {
            return false;
        }
    } else {
        File fp(ZSTR_VAL(file), O_RDONLY);
        if (!fp.ready()) {
            return false;
        }
        body = fp.read_content();
    }
    body->length = SW_MIN(length, body->length);

    zval *ztrailer =
        sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0);
    if (php_swoole_array_length_safe(ztrailer) == 0) {
        ztrailer = nullptr;
    }

    zval *zheader =
        sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0);
    if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) {
        ctx->set_header(ZEND_STRL("content-type"), swoole::mime_type::get({ZSTR_VAL(file), ZSTR_LEN(file)}), false);
    }

    bool end_stream = (ztrailer == nullptr);
    if (!stream->send_header(body.get(), end_stream)) {
        return false;
    }

    /* headers has already been sent, retries are no longer allowed (even if send body failed) */
    ctx->end_ = 1;

    bool error = false;

    if (body->length > 0) {
        if (!stream->send_body(body.get(), end_stream, client, offset, length)) {
            error = true;
        }
    }

    if (!error && ztrailer) {
        if (!stream->send_trailer()) {
            error = true;
        }
    }

    if (error) {
        ctx->close(ctx);
    } else {
        client->remove_stream(stream->id);
    }

    return true;
}

static bool http2_server_onBeforeRequest(HttpContext *ctx) {
    auto serv = ctx->get_async_server();
    if (serv->is_unavailable()) {
        String null_body{};
        ctx->response.status = SW_HTTP_SERVICE_UNAVAILABLE;
        http2_server_respond(ctx, &null_body);
        zval_ptr_dtor(ctx->request.zobject);
        zval_ptr_dtor(ctx->response.zobject);
        return false;
    }
    return swoole_http_server_onBeforeRequest(ctx);
}

static int http2_server_parse_header(
    const std::shared_ptr<Http2Session> &client, HttpContext *ctx, int flags, const char *in, size_t inlen) {
    nghttp2_hd_inflater *inflater = client->inflater;

    if (!inflater) {
        int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem());
        if (ret != 0) {
            swoole_warning("nghttp2_hd_inflate_new2() failed, Error: %s[%d]", nghttp2_strerror(ret), ret);
            return SW_ERR;
        }
        client->inflater = inflater;
    }

    if (flags & SW_HTTP2_FLAG_PRIORITY) {
        // int stream_deps = ntohl(*(int *) (in));
        // uint8_t weight = in[4];
        in += 5;
        inlen -= 5;
    }

    zval *zheader = ctx->request.zheader;
    zval *zserver = ctx->request.zserver;

    for (;;) {
        nghttp2_nv nv;
        int inflate_flags = 0;

        ssize_t rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1);
        if (rv < 0) {
            swoole_warning("inflate failed, Error: %s[%zd]", nghttp2_strerror(rv), rv);
            return SW_ERR;
        }

        auto proclen = (size_t) rv;

        in += proclen;
        inlen -= proclen;

        if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
            swoole_trace_log(SW_TRACE_HTTP2,
                             "name=(%zu)[" SW_ECHO_BLUE "], value=(%zu)[" SW_ECHO_CYAN "]",
                             nv.namelen,
                             nv.name,
                             nv.valuelen,
                             nv.value);

            if (nv.name[0] == ':') {
                if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "method")) {
                    add_assoc_stringl_ex(zserver, ZEND_STRL("request_method"), (char *) nv.value, nv.valuelen);
                } else if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "path")) {
                    char *pathbuf = sw_tg_buffer()->str;
                    char *v_str = strchr((char *) nv.value, '?');
                    zend_string *zstr_path;
                    if (v_str) {
                        v_str++;
                        int k_len = v_str - (char *) nv.value - 1;
                        int v_len = nv.valuelen - k_len - 1;
                        memcpy(pathbuf, nv.value, k_len);
                        pathbuf[k_len] = 0;
                        add_assoc_stringl_ex(zserver, ZEND_STRL("query_string"), v_str, v_len);
                        zstr_path = zend_string_init(pathbuf, k_len, false);
                        // parse url params
                        sapi_module.treat_data(
                            PARSE_STRING,
                            estrndup(v_str, v_len),  // it will be freed by treat_data
                            swoole_http_init_and_read_property(
                                swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, ZEND_STRL("get")));
                    } else {
                        zstr_path = zend_string_init((char *) nv.value, nv.valuelen, false);
                    }
                    ctx->request.path = estrndup((char *) nv.value, nv.valuelen);
                    ctx->request.path_len = nv.valuelen;
                    add_assoc_str_ex(zserver, ZEND_STRL("request_uri"), zstr_path);
                    // path_info should be decoded
                    zstr_path = zend_string_dup(zstr_path, false);
                    ZSTR_LEN(zstr_path) = php_url_decode(ZSTR_VAL(zstr_path), ZSTR_LEN(zstr_path));
                    add_assoc_str_ex(zserver, ZEND_STRL("path_info"), zstr_path);
                } else if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "authority")) {
                    add_assoc_stringl_ex(zheader, ZEND_STRL("host"), (char *) nv.value, nv.valuelen);
                }
            } else {
                if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-type")) {
                    if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "application/x-www-form-urlencoded")) {
                        ctx->request.post_form_urlencoded = 1;
                    } else if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "multipart/form-data")) {
                        size_t offset = sizeof("multipart/form-data") - 1;
                        char *boundary_str;
                        int boundary_len;
                        if (!ctx->get_multipart_boundary(
                                (char *) nv.value, nv.valuelen, offset, &boundary_str, &boundary_len)) {
                            return SW_ERR;
                        }
                        ctx->init_multipart_parser(boundary_str, boundary_len);
                        ctx->parser.data = ctx;
                    }
                } else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "cookie")) {
                    swoole_http_parse_cookie(
                        swoole_http_init_and_read_property(
                            swoole_http_request_ce, ctx->request.zobject, &ctx->request.zcookie, ZEND_STRL("cookie")),
                        (const char *) nv.value,
                        nv.valuelen);
                    continue;
                } else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-length")) {
                    char *end;
                    zend_long content_length = std::strtol((char *) nv.value, &end, 10);
                    if (end != (char *) nv.value + nv.valuelen || content_length > client->max_body_size) {
                        http2_server_send_status_code(ctx, SW_HTTP_REQUEST_ENTITY_TOO_LARGE);
                    }
                }
#ifdef SW_HAVE_COMPRESSION
                else if (ctx->enable_compression && SW_STRCASEEQ((char *) nv.name, nv.namelen, "accept-encoding")) {
                    ctx->set_compression_method((char *) nv.value, nv.valuelen);
                }
#endif
                add_assoc_stringl_ex(zheader, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen);
            }
        }

        if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
            nghttp2_hd_inflate_end_headers(inflater);
            break;
        }

        if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) {
            break;
        }
    }

    return SW_OK;
}

int swoole_http2_server_parse(const std::shared_ptr<Http2Session> &client, const char *buf) {
    int type = buf[3];
    int flags = buf[4];

    uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff;

    if (stream_id > client->last_stream_id) {
        client->last_stream_id = stream_id;
    }

    if (client->shutting_down) {
        swoole_error_log(
            SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_IGNORE, "ignore http2 stream#%d after sending goaway", stream_id);
        return SW_ERR;
    }

    ssize_t length = Http2::get_length(buf);
    buf += SW_HTTP2_FRAME_HEADER_SIZE;

    uint32_t value = 0;

    switch (type) {
    case SW_HTTP2_TYPE_SETTINGS: {
        uint16_t id = 0;
        if (flags & SW_HTTP2_FLAG_ACK) {
            swoole_http2_frame_trace_log("ACK");
            break;
        }

        while (length > 0) {
            id = ntohs(*(uint16_t *) (buf));
            value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t)));
            swoole_http2_frame_trace_log("id=%d, value=%d", id, value);
            switch (id) {
            case SW_HTTP2_SETTING_HEADER_TABLE_SIZE:
                if (value != client->remote_settings.header_table_size) {
                    client->remote_settings.header_table_size = value;
                    if (client->deflater) {
                        int ret = nghttp2_hd_deflate_change_table_size(client->deflater, value);
                        if (ret != 0) {
                            swoole_warning("nghttp2_hd_deflate_change_table_size() failed, errno=%d, errmsg=%s",
                                           ret,
                                           nghttp2_strerror(ret));
                            return SW_ERR;
                        }
                    }
                }
                swoole_trace_log(SW_TRACE_HTTP2, "setting: header_table_size=%u", value);
                break;
            case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
                client->remote_settings.max_concurrent_streams = value;
                swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value);
                break;
            case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE:
                client->remote_window_size = client->remote_settings.init_window_size = value;
                swoole_trace_log(SW_TRACE_HTTP2, "setting: init_window_size=%u", value);
                break;
            case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE:
                client->remote_settings.max_frame_size = value;
                swoole_trace_log(SW_TRACE_HTTP2, "setting: max_frame_size=%u", value);
                break;
            case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
                client->remote_settings.max_header_list_size = value;  // useless now
                swoole_trace_log(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value);
                break;
            default:
                // disable warning and ignore it because some websites are not following http2 protocol totally
                // swoole_warning("unknown option[%d]: %d", id, value);
                break;
            }
            buf += sizeof(id) + sizeof(value);
            length -= sizeof(id) + sizeof(value);
        }
        // After receiving the setting frame sent by the client, it is necessary to reply with an ACK,
        // otherwise the client will assume that the server has not applied the setting
        http2_server_send_setting_ack(client->default_ctx);
        break;
    }
    case SW_HTTP2_TYPE_HEADERS: {
        auto stream = client->get_stream(stream_id);
        swoole_http2_frame_trace_log("%s", (stream ? "exist stream" : "new stream"));
        if (!stream) {
            stream = client->create_stream(stream_id);
            if (!stream) {
                return SW_ERR;
            }
        }
        HttpContext *ctx = stream->ctx;
        if (http2_server_parse_header(client, ctx, flags, buf, length) < 0) {
            return SW_ERR;
        }

        if (flags & SW_HTTP2_FLAG_END_STREAM) {
            client->handle(client, stream);
        } else {
            // need continue frame
        }
        break;
    }
    case SW_HTTP2_TYPE_DATA: {
        swoole_http2_frame_trace_log("data");
        auto stream = client->get_stream(stream_id);
        if (!stream) {
            swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NOT_FOUND, "http2 stream#%d not found", stream_id);
            return SW_ERR;
        }

        HttpContext *ctx = stream->ctx;

        if (length > 0) {
            auto buffer = ctx->get_http2_data_buffer();

            // Exceeded the max_body_size, or if the stream has ended, sends RST_STREAM frame, stops receiving data
            if (buffer->length + length > client->max_body_size || ctx->end_) {
                http2_server_send_rst_stream(ctx, 0);
                client->remove_stream(stream_id);
                break;
            }

            buffer->append(buf, length);

            // flow control
            client->local_window_size -= length;
            stream->local_window_size -= length;

            if (client->local_window_size < (client->local_settings.init_window_size / 4)) {
                http2_server_send_window_update(
                    ctx, 0, client->local_settings.init_window_size - client->local_window_size);
                client->local_window_size = client->local_settings.init_window_size;
            }
            if (stream->local_window_size < (client->local_settings.init_window_size / 4)) {
                http2_server_send_window_update(
                    ctx, stream_id, client->local_settings.init_window_size - stream->local_window_size);
                stream->local_window_size = client->local_settings.init_window_size;
            }
        }

        if (flags & SW_HTTP2_FLAG_END_STREAM) {
            if (ctx->get_http2_data_length() > 0) {
                auto buffer = ctx->get_http2_data_buffer();
                if (ctx->parse_body && ctx->request.post_form_urlencoded) {
                    auto post_prop = swoole_http_init_and_read_property(
                        swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post"));
                    // it will be freed by sapi_module.treat_data()
                    auto post_str = estrndup(buffer->str, buffer->length);
                    sapi_module.treat_data(PARSE_STRING, post_str, post_prop);
                } else if (ctx->mt_parser != nullptr) {
                    if (!ctx->parse_multipart_data(buffer->str, buffer->length)) {
                        return SW_ERR;
                    }
                }
            }
            client->handle(client, stream);
        }
        break;
    }
    case SW_HTTP2_TYPE_PING: {
        swoole_http2_frame_trace_log("ping");
        if (!(flags & SW_HTTP2_FLAG_ACK)) {
            char ping_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
            Http2::set_frame_header(
                ping_frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id);
            memcpy(ping_frame + SW_HTTP2_FRAME_HEADER_SIZE, buf, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
            client->default_ctx->send(
                client->default_ctx, ping_frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
        }
        break;
    }
    case SW_HTTP2_TYPE_WINDOW_UPDATE: {
        value = ntohl(*(uint32_t *) buf);
        if (stream_id == 0) {
            client->remote_window_size += value;
        } else {
            auto stream = client->get_stream(stream_id);
            if (stream) {
                stream->remote_window_size += value;
                if (!client->is_coro && stream->waiting_coroutine) {
                    stream->waiting_coroutine->resume();
                }
            }
        }
        swoole_http2_frame_trace_log("window_size_increment=%d", value);
        break;
    }
    case SW_HTTP2_TYPE_RST_STREAM: {
        value = ntohl(*(int *) (buf));
        swoole_http2_frame_trace_log("error_code=%d", value);
        client->remove_stream(stream_id);
        break;
    }
    case SW_HTTP2_TYPE_GOAWAY: {
        uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf));
        buf += 4;
        value = ntohl(*(uint32_t *) (buf));
        buf += 4;
        swoole_http2_frame_trace_log("last_stream_id=%d, error_code=%d, opaque_data=[%.*s]",
                                     server_last_stream_id,
                                     value,
                                     (int) (length - SW_HTTP2_GOAWAY_SIZE),
                                     buf);
        // TODO: onRequest
        (void) server_last_stream_id;

        break;
    }
    default: {
        swoole_http2_frame_trace_log("");
    }
    }

    return SW_OK;
}

int swoole_http2_server_onReceive(Server *serv, Connection *conn, RecvData *req) {
    SessionId session_id = req->info.fd;
    auto iter = http2_sessions.find(session_id);
    std::shared_ptr<Http2Session> client;
    if (iter == http2_sessions.end()) {
        client = swoole_http2_server_session_new(session_id);
        client->default_ctx = new HttpContext();
        client->default_ctx->init(serv);
        client->default_ctx->fd = session_id;
        client->default_ctx->http2 = true;
        client->default_ctx->keepalive = true;
        client->default_ctx->onBeforeRequest = http2_server_onBeforeRequest;
        client->max_body_size = serv->get_package_max_length(conn);
        client->handle = http2_server_onRequest;
        http2_sessions.emplace(session_id, client);
    } else {
        client = iter->second;
    }

    zval zdata;
    php_swoole_get_recv_data(serv, &zdata, req);
    int retval = swoole_http2_server_parse(client, Z_STRVAL(zdata));
    zval_ptr_dtor(&zdata);

    return retval;
}

std::shared_ptr<swoole::http2::Session> swoole_http2_server_session_new(swoole::SessionId fd) {
    auto session = std::make_shared<Http2Session>(fd);
    http2_sessions.emplace(fd, session);
    return session;
}

void swoole_http2_server_session_free(swoole::SessionId fd) {
    auto iter = http2_sessions.find(fd);
    if (iter == http2_sessions.end()) {
        return;
    }
    /* default_ctx does not blong to session object */
    iter->second->default_ctx = nullptr;
    http2_sessions.erase(iter);
}
